#include "ptwrap.hpp"
#include <deque>
#include <vector>
#include <assert.h>


// [[ex04ae]]
// DEBUG descr The queue with a simple message batching.
template <class Value>
class Queue
{
public:
// DEBUG {
	// a negative value of the low water mark would
	// cause the default of 2/3 of the high mark to be
	// used
	Queue(int hiwat = 1000, int lowat = -1) :
		hiwat_(hiwat), 
		lowat_(lowat >= 0? lowat : hiwat_*2/3), 
		overcond_(cond_), onhimark_(false)
	{ }

	virtual ~Queue()
	{ }

	// this combines the STL front() and pop_front()
	// since the popping must be atomic
	Value pop_front()
	{
		pw::lockmutex lm(cond_);

		while(deque_.empty())
			// it releases and gets back the mutex
			cond_.wait(); 

		Value v = deque_.front();
		deque_.pop_front();

		// <= lowat_ works even if lowat_ == 0
		if (onhimark_ && deque_.size() <= lowat_ ) {
			// reached the low mark, may continue
			onhimark_ = false; 
			overcond_.broadcast();
		}

		return v;
	}

	void push_back(Value &v)
	{
		pw::lockmutex lm(cond_);

		if (deque_.size() >= hiwat_)
			onhimark_ = true;
		// another thread might have reached the high
		// mark first but we still should not post until
		// the queue drops to the low mark
		while (onhimark_) {
			// it releases and gets back the mutex
			overcond_.wait(); 
			// must check again, since another thread
			// might have gotten in
			if (deque_.size() >= hiwat_)
				onhimark_ = true;
		}

		bool wasempty = deque_.empty();
		deque_.push_back(v);
		if (wasempty) 
			// we know that the queue has just become
			// non-empty
			cond_.broadcast();
	}
// DEBUG }
	// ...

	// returns true on success, false if the queue is
	// empty
	bool try_pop_front(Value &result)
	{
		pw::lockmutex lm(cond_);

		if(deque_.empty())
			return false;

		result = deque_.front();
		deque_.pop_front();

		return true;
	}

	void push_back_batch(std::vector<Value> &v)
	{
		pw::lockmutex lm(cond_);

		for (size_t i = 0; i < v.size(); i++) {
			if (deque_.size() >= hiwat_)
				onhimark_ = true;
			// another thread might have reached the high
			// mark but we still should not post until the
			// queue drops to the low mark
			while (onhimark_) {
				// it releases and gets back the mutex
				overcond_.wait(); 
				// must check again, since another thread
				// might have gotten in
				if (deque_.size() >= hiwat_)
					onhimark_ = true;
			}

			bool wasempty = deque_.empty();
			deque_.push_back(v[i]);
			if (wasempty != 0) 
				// we know that the queue has just become
				// non-empty
				cond_.broadcast();
		}
		v.clear();
	}
// ...
// DEBUG {
protected:
	int hiwat_, lowat_;
	std::deque<Value> deque_;
	pw::pmcond cond_;
	pw::pchaincond overcond_;
	bool onhimark_; // flag that the queue has reached the high mark
// DEBUG }
};
// END

// DEBUG {
int main()
{
	Queue<int> q;
	std::vector <int> batch;
	batch.push_back(2);
	batch.push_back(3);
	int v1 = 1;
	q.push_back(v1);
	q.push_back_batch(batch);
	int v2 = q.pop_front();
	assert(v2 == 1);
	assert(q.try_pop_front(v2));
	assert(v2 == 2);
	assert(q.try_pop_front(v2));
	assert(v2 == 3);
	assert(!q.try_pop_front(v2));
	return 0;
}
// DEBUG }
